﻿using System;
using System.Collections.Generic;
using Inovout.IdeaSoft.Model;
using System.Xml.Linq;
using System.Linq;

namespace Inovout.IdeaSoft
{
    public class EAUmlParser : IParser
    {
        #region 字段

        private XElement xElement;
        private ProjectInfo projectInfo;
        private readonly XNamespace xmiPrefixNamespace;
        private readonly XNamespace umlPrefixNamespace;
        private Dictionary<string, TypeInfo> typeInfo_Ids = new Dictionary<string,TypeInfo>();
        #endregion

        #region 构造函数
        public EAUmlParser(string path)
        {
            if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");
            if (!System.IO.File.Exists(path)) throw new System.IO.FileNotFoundException(path);

            xElement = XElement.Load(path);
            xmiPrefixNamespace = xElement.GetNamespaceOfPrefix("xmi");
            umlPrefixNamespace = xElement.GetNamespaceOfPrefix("uml");
        }
        #endregion       
        
        #region IParser 成员
        /// <summary>
        /// 解析返回蓝图
        /// </summary>
        /// <returns></returns>
        public ProjectInfo Parse()
        {
            projectInfo = new ProjectInfo();
            projectInfo.Types = GetTypeInfos();
            BuildRelationInfos();
            projectInfo.Modules = BuildModules();
            return projectInfo;
        }

        #endregion
        private ModifyAccess ConvertToModifyAccess(string value)
        {
            switch (value)
            {
                case "public": return ModifyAccess.Public;
                case "privite": return ModifyAccess.Private;
                case "protected": return ModifyAccess.Protected;
                case "internal": return ModifyAccess.Internal;
                case "protectedinternal": return ModifyAccess.ProtectedAndInternal;
                default:
                    return ModifyAccess.None;
            }
        }
        /// <summary>
        /// 获取类型集合
        /// </summary>
        /// <returns></returns>
        private ITypeInfo[] GetTypeInfos()
        {

            var list = from el in xElement.Descendants()
                       let type = el.Attribute(xmiPrefixNamespace + UMLKeyStruct.type) == null ? string.Empty : el.Attribute(xmiPrefixNamespace + UMLKeyStruct.type).Value
                       where (el.Name == UMLKeyStruct.packagedElement) && (type == "uml:Class" || type == "uml:Interface" || type == "uml:Enumeration")
                       select el;
            var typeInfos = new List<TypeInfo>();
            TypeInfo typeInfo = null;
            string id = string.Empty;
            foreach (var item in list)
            {
                switch (item.Attribute(xmiPrefixNamespace + UMLKeyStruct.type).Value)
                {
                    case "uml:Class": typeInfo = new ClassInfo(); break;
                    case "uml:Interface": typeInfo = new InterfaceInfo(); break;
                    case "uml:Enumeration": typeInfo = new EnumInfo(); break;
                    default: typeInfo = null;
                        break;
                }
                if (typeInfo != null)
                {
                    id = item.Attribute(xmiPrefixNamespace + UMLKeyStruct.id).Value;
                    typeInfo.Name = item.Attribute(UMLKeyStruct.name).Value;
                    typeInfo.Alias = item.Attribute(UMLKeyStruct.name).Value;
                    typeInfo.ModifyAccess = ConvertToModifyAccess(item.Attribute(UMLKeyStruct.visibility).Value);
                    typeInfos.Add(typeInfo);
                    if(!string.IsNullOrEmpty(id))
                    {
                        typeInfo_Ids.Add(id, typeInfo);                        
                    }
                }
            }
            projectInfo.FreamworkTypes = GetKeyWordDataType().Concat(GetDotNetFrameworkDataType()).ToArray();
            projectInfo.Types = typeInfos.ToArray();
            return typeInfos.Concat(projectInfo.FreamworkTypes.Cast<ITypeInfo>()).ToArray();
        }
        private IList<FreamworkTypeInfo> GetKeyWordDataType()
        {
            IList<FreamworkTypeInfo> dataTypes = new List<FreamworkTypeInfo>();
            dataTypes.Add(new FreamworkTypeInfo { Name = "void"});
            dataTypes.Add(new FreamworkTypeInfo {Name = "bool"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "byte"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "sbyte"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "char"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "decimal"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "double"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "float"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "int"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "uint"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "long"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "ulong"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "object"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "short"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "ushort"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "string"});

            dataTypes.Add(new FreamworkTypeInfo { Name = "bool?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "byte?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "sbyte?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "char?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "decimal?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "double?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "float?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "int?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "uint?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "long?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "ulong?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "short?"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "ushort?"});
            return dataTypes;
        }

        private IList<FreamworkTypeInfo> GetDotNetFrameworkDataType()
        {
            IList<FreamworkTypeInfo> dataTypes = new List<FreamworkTypeInfo>();
            dataTypes.Add(new FreamworkTypeInfo { Name = "Boolean", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Byte", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "SByte", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Char", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "DateTime", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Decimal", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Double", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Float", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Guid", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Int", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "UInt", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Long", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "ULong", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Object", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Short", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "UShort", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "String", Namespace = "System"});

            dataTypes.Add(new FreamworkTypeInfo { Name = "Boolean?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Byte?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "SByte?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Char?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "DateTime?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Decimal?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Double?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Float?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Guid?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Int?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "UInt?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Long?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "ULong?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "Short?", Namespace = "System"});
            dataTypes.Add(new FreamworkTypeInfo { Name = "UShort?", Namespace = "System"});
            return dataTypes;
        }
        /// <summary>
        /// 构造关系集合
        /// </summary>
        /// <param name="el"></param>
        /// <returns></returns>
        private void BuildRelationInfos()
        {
            var list = from el in xElement.Descendants()
                       where el.Name == UMLKeyStruct.packagedElement && el.Attribute(xmiPrefixNamespace + UMLKeyStruct.type) != null && el.Attribute(xmiPrefixNamespace + UMLKeyStruct.type).Value == "uml:Realization"
                       select el;

            IList<RelationInfo> relationInfos = new List<RelationInfo>();
            foreach (var el in list)
            {
                switch (el.Attribute(xmiPrefixNamespace + UMLKeyStruct.type).Value)
                {
                    case "Realization": relationInfos.Add(new RealizationInfo()
                    {
                        SourceType = GetTypeInfo(el.Attribute(UMLKeyStruct.supplier).Value),
                        TargetType = GetTypeInfo(el.Attribute(UMLKeyStruct.client).Value)
                    }); break;
                    case "Composition": relationInfos.Add(new CompositionInfo()
                    {
                        SourceType = GetTypeInfo(el.Attribute(UMLKeyStruct.supplier).Value),
                        TargetType = GetTypeInfo(el.Attribute(UMLKeyStruct.client).Value)
                    }); break;
                    case "Aggregation": relationInfos.Add(new AggregationInfo()
                    {
                        SourceType = GetTypeInfo(el.Attribute(UMLKeyStruct.supplier).Value),
                        TargetType = GetTypeInfo(el.Attribute(UMLKeyStruct.client).Value)
                    }); break;
                    case "Generalization": relationInfos.Add(new GeneralizationInfo()
                    {
                        SourceType = GetTypeInfo(el.Attribute(UMLKeyStruct.supplier).Value),
                        TargetType = GetTypeInfo(el.Attribute(UMLKeyStruct.client).Value)
                    }); break;
                    default:
                        break;
                }
            }
            TypeInfo typeInfo = null;
            foreach (var item in relationInfos)
            {

                ITypeInfo ti = item.SourceType;
                if (typeInfo.Relations != null) continue;
                if (!(ti is TypeInfo)) continue;
                typeInfo = ti as TypeInfo;
                typeInfo.Relations = (from n in relationInfos
                                          where n.SourceType == typeInfo || n.TargetType == typeInfo
                                          select n).ToArray();
                ti = item.TargetType;
                if (typeInfo.Relations != null) continue;
                if (!(ti is TypeInfo)) continue;
                typeInfo = ti as TypeInfo;
                typeInfo.Relations = (from n in relationInfos
                                          where n.SourceType == typeInfo || n.TargetType == typeInfo
                                          select n).ToArray();
            }
        }
        /// <summary>
        /// 构造模块集合
        /// </summary>
        /// <returns></returns>
        private ModuleInfo[] BuildModules()
        {
            var els = from el_base in xElement.Elements()
                      where el_base.Name.LocalName == UMLKeyStruct.model
                      select el_base;
            return (from el in els.First().Elements()
                    where el.Name == UMLKeyStruct.packagedElement && el.Attribute(xmiPrefixNamespace + UMLKeyStruct.type).Value == "uml:Package"
                    let moduleInfo = new ModuleInfo()
                    {
                        Components = BuildComponentInfos(el),
                        ProjectInfo = projectInfo,
                        Name = el.Attribute(UMLKeyStruct.name).Value,
                        Alias = el.Attribute(UMLKeyStruct.name).Value,
                    }
                    select moduleInfo).ToArray();
        }
        /// <summary>
        /// 构造组件集合
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private ComponentInfo[] BuildComponentInfos(XElement xElement)
        {
            return (from el in xElement.Elements()
                    where el.Name == UMLKeyStruct.packagedElement && el.Attribute(xmiPrefixNamespace + UMLKeyStruct.type).Value == "uml:Package"
                    let componentInfo = new ComponentInfo()
                    {
                        Name = el.Attribute(UMLKeyStruct.name).Value,
                        Alias = el.Attribute(UMLKeyStruct.name).Value,
                        Packages = BuildPackageInfos(el)
                    }
                    select componentInfo
            ).ToArray();
        }
        /// <summary>
        /// 构造包集合
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private PackageInfo[] BuildPackageInfos(XElement xElement)
        {
            return (from el in xElement.Elements()
                    where el.Name == UMLKeyStruct.packagedElement
                    select new PackageInfo()
                    {
                        Name = el.Attribute(UMLKeyStruct.name).Value,
                        Alias = el.Attribute(UMLKeyStruct.name).Value,
                        Types = BuildTypeInfos(el)
                    }).ToArray();
        }
        /// <summary>
        /// 构造类型集合
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private TypeInfo[] BuildTypeInfos(XElement xElement)
        {
            return (from el in xElement.Elements()
                    where el.Name == UMLKeyStruct.packagedElement
                    select BuildTypeInfo(el)
                        ).Where(f => f != null).ToArray();
        }
        /// <summary>
        /// 构造类型
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private TypeInfo BuildTypeInfo(XElement xElement)
        {
            XAttribute attr = xElement.Attribute(xmiPrefixNamespace + UMLKeyStruct.id);
            if (attr == null) return null;
            string id = attr.Value;
            TypeInfo typeInfo = GetTypeInfo(id) as TypeInfo;
            if (typeInfo == null) return null;
            typeInfo.Members = BuildMemberInfos(xElement, typeInfo);
            return typeInfo;
        }
        /// <summary>
        /// 构造成员集合
        /// </summary>
        /// <param name="xElement"></param>
        /// <param name="typeInfo"></param>
        /// <returns></returns>
        private MemberInfo[] BuildMemberInfos(XElement xElement, TypeInfo typeInfo)
        {
            IEnumerable<MemberInfo> memberInfos = new MemberInfo[0];
            if (typeInfo is ClassInfo)
            {
                ClassInfo classInfo = typeInfo as ClassInfo;
                classInfo.Constructors = BuildConstructorInfos(xElement);
                classInfo.Properties = BuildPropertyInfos(xElement);
                classInfo.Methods = BuildMethodInfos(xElement);
                classInfo.Fields = BuildFieldInfos(xElement);
                classInfo.Events = BuildEventInfos(xElement);

                memberInfos = memberInfos.Concat(classInfo.Constructors)
                             .Concat(classInfo.Properties)
                             .Concat(classInfo.Methods)
                             .Concat(classInfo.Fields)
                             .Concat(classInfo.Events);
            }
            if (typeInfo is InterfaceInfo)
            {
                InterfaceInfo interfaceInfo = typeInfo as InterfaceInfo;
                interfaceInfo.Properties = BuildPropertyInfos(xElement);
                interfaceInfo.Methods = BuildMethodInfos(xElement);
                interfaceInfo.Events = BuildEventInfos(xElement);

                memberInfos = memberInfos.Concat(interfaceInfo.Properties)
                               .Concat(interfaceInfo.Methods)
                               .Concat(interfaceInfo.Events);
            }
            if (typeInfo is EnumInfo)
            {
                EnumInfo enumInfo = typeInfo as EnumInfo;
                enumInfo.Items = BuildEnumOptions(xElement);

                memberInfos = memberInfos.Concat(enumInfo.Items);
            }
            return memberInfos.ToArray();
        }

        private EnumItemInfo[] BuildEnumOptions(XElement xElement)
        {
            return (from el in xElement.Elements()
                    where el.Name.LocalName == UMLKeyStruct.ownedLiteral || el.Attribute(UMLKeyStruct.type).Value == "uml:EnumerationLiteral"
                    select new EnumItemInfo()
                    {
                        Name = el.Attribute(UMLKeyStruct.name).Value,
                        Alias = el.Attribute(UMLKeyStruct.name).Value,
                        ModifyAccess = ConvertToModifyAccess(el.Attribute(UMLKeyStruct.visibility).Value)
                    }).ToArray();
        }
        /// <summary>
        /// 构造事件集合
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private EventInfo[] BuildEventInfos(XElement xElement)
        {
            return new EventInfo[0];
        }
        /// <summary>
        /// 构造字段集合
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private FieldInfo[] BuildFieldInfos(XElement xElement)
        {
            return new FieldInfo[0];
        }
        /// <summary>
        /// 构建方法集合
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private MethodInfo[] BuildMethodInfos(XElement xElement)
        {
            return (from el in xElement.Elements()
                    where el.Name == UMLKeyStruct.ownedOperation
                    select new MethodInfo()
                    {
                        Name = el.Attribute(UMLKeyStruct.name).Value,
                        Alias = el.Attribute(UMLKeyStruct.name).Value,
                        ModifyAccess = ConvertToModifyAccess(el.Attribute(UMLKeyStruct.visibility).Value),
                        Parameters = BuildParameterInfos(el),
                        ReturnType = GetReturnType(el)
                    }).ToArray();
        }
        /// <summary>
        /// 获取返回值类型
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private ITypeInfo GetReturnType(XElement xElement)
        {
            return (from el in xElement.Elements()
                    where el.Name == UMLKeyStruct.ownedParameter && el.Attribute(UMLKeyStruct.name).Value == "return"
                    select GetTypeInfo(el.Attribute(UMLKeyStruct.type).Value)
                    ).FirstOrDefault();
        }
        /// <summary>
        /// 构建参数集合根据xml节点
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private ParameterInfo[] BuildParameterInfos(XElement xElement)
        {
            return (from el in xElement.Elements()
                    where el.Name == UMLKeyStruct.ownedParameter && el.Attribute(UMLKeyStruct.name).Value != "return"
                    select new ParameterInfo()
                    {
                        IsIn = false,
                        IsOut = false,
                        TypeInfo = GetTypeInfo(el.Attribute(UMLKeyStruct.type).Value)
                    }).ToArray();
        }
        /// <summary>
        /// 构建属性集合
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private PropertyInfo[] BuildPropertyInfos(XElement xElement)
        {
            return (from el in xElement.Elements()
                    where el.Name == UMLKeyStruct.ownedAttribute && el.Attribute(xmiPrefixNamespace + UMLKeyStruct.type).Value == "uml:Property" && el.Attribute(UMLKeyStruct.name) != null
                    select new PropertyInfo()
                    {
                        Name = el.Attribute(UMLKeyStruct.name).Value,
                        Alias = el.Attribute(UMLKeyStruct.name).Value,
                        ModifyAccess = ConvertToModifyAccess(el.Attribute(UMLKeyStruct.visibility).Value),
                        Type = GetTypeInfo(el.Elements(UMLKeyStruct.type).First().Attribute(xmiPrefixNamespace + UMLKeyStruct.idref).Value)
                    }).ToArray();
        }
        /// <summary>
        /// 获取类型根据指定值
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private ITypeInfo GetTypeInfo(string value)
        {
            ITypeInfo typeInfo = null;
            if (value.StartsWith("EAJava_"))
            {
                value = value.Substring(7);
                typeInfo = projectInfo.FreamworkTypes.FirstOrDefault((innerDataType) => { return innerDataType.FullName == value || innerDataType.Name == value; });
            }
            if (value.StartsWith("EAID_"))
            {
                typeInfo = typeInfo_Ids.FirstOrDefault(f => f.Key == value).Value;
            }
            return typeInfo;
        }
        /// <summary>
        /// 构建构造函数
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private ConstructorInfo[] BuildConstructorInfos(XElement xElement)
        {
            return new ConstructorInfo[0];
        }


    }
}
